package com.sjzd.net;

import com.sjzd.message.Protocol;

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 *  游戏服务器
 *  1.连接客户端
 *  2.给客户端分配随机id
 *  3.将信息发送到客户端
 */
public class ServerNet {
    private static final Integer TCP_PORT = 8080;
    private static final Integer UDP_PORT = 8888;
    private ServerSocketChannel ssc = null;
    private DatagramChannel dc = null;
    private Selector selector = null;
    private ByteBuffer tcpBuf = ByteBuffer.allocate(1024);
    private ByteBuffer udpBuf = ByteBuffer.allocate(1024);
    private Map<String, Integer> clientAddressMap =  // String ： ip, Integer : 端口号
            new ConcurrentHashMap<>(50);

    /**
     *  开启服务器
     */
    public void turnOn() {
        try {
            /* TCP **/
            ssc = ServerSocketChannel.open();
            ssc.bind(new InetSocketAddress(TCP_PORT));
            ssc.configureBlocking(false);
            selector = Selector.open();
            ssc.register(selector, SelectionKey.OP_ACCEPT);
            /* UDP **/
            dc = DatagramChannel.open();
            dc.bind(new InetSocketAddress(UDP_PORT));
        } catch (IOException e) {
            System.err.println("服务器启动失败!");
            e.printStackTrace();
            turnOff();
        }
    }

    /**
     *  服务器tcp运行监测
     */
    public void tcpMonitor() {
        try {
            while (true) {
                if (selector.select() > 0) {
                    Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                    while (iterator.hasNext()) {
                        SelectionKey next = iterator.next();
                        if (next.isAcceptable()) {
                            System.out.println("有客户端请求连接");
                            processAccept();
                        } else if (next.isReadable()){
                            registerClient((SocketChannel)next.channel());
                        } else {
                            System.out.println("writeable writeable");
                        }
                        iterator.remove();
                    }
                }
            }
        } catch(Exception e) {
            System.err.println("服务器运行过程中出现错误！");
            turnOff();
        }
    }

    /**
     *  服务器udp运行监测
     *      转发所有客户端传来的udp包
     */
    public void udpMonitor() {
        SocketAddress clientAddress = null;
        InetSocketAddress socketAddress = null;
        try {
            while (true) {
                udpBuf.clear();
                if (null != (clientAddress = dc.receive(udpBuf))) {
                    for (Map.Entry<String, Integer> entry : clientAddressMap.entrySet()) {
                       socketAddress = new InetSocketAddress(entry.getKey(), entry.getValue());
                       if (!getIPFromSocketAddress(clientAddress).equals(entry.getKey())) {
                           dc.send(udpBuf, socketAddress);
                       }
                    }
                }
            }
        } catch (Exception e) {
            System.err.println("UDP接收客户端信息出现错误！");
        }
    }



    /**
     *  注册成功链接的客户端地址信息
     * @param clientChannel
     */
    private void registerClient(SocketChannel clientChannel) {
        tcpBuf.clear();
        try {
            int len = 0;
            tcpBuf.flip();
            while ((len = clientChannel.read(tcpBuf)) > 0) {
                String msg = new String(tcpBuf.array(), 0, len);
                if (msg.startsWith(Protocol.CONNECT_SYMBOL.getSymbol()) &&
                        msg.endsWith(Protocol.CONNECT_SYMBOL.getSymbol())) {
                    String clientPort = msg.substring(Protocol.CONNECT_SYMBOL.getSymbol().length(),
                            msg.length() - Protocol.CONNECT_SYMBOL.getSymbol().length());
                    clientAddressMap.put(getIPFromSocketAddress(clientChannel.getRemoteAddress()), Integer.parseInt(clientPort));
                }
            }
        } catch (IOException e) {
            System.err.println("TCP接收来自客户端信息时出现错误!" + e.getMessage());
            e.printStackTrace();
        }
    }

    /**
     *  关闭服务器
     */
    public void turnOff() {
        try {
            if (null != ssc) {
                ssc.close();
            }
            if (null != selector) {
                selector.close();
            }
            if (null != dc) {
                dc.close();
            }

        } catch (Exception e) {
            System.err.println("服务器关闭异常!" + e.getMessage());
            turnOff();
        }
    }

    /**
     *  处理客户端的连接请求
     */
    public void processAccept() {
        try {
            SocketChannel clientChannel = ssc.accept();
            clientChannel.configureBlocking(false);
            clientChannel.register(selector, SelectionKey.OP_READ);
            SocketAddress remoteAddress = clientChannel.getRemoteAddress();
            /* for test remoteAddress = /127.0.0.1:57007**/
//            System.out.println("remoteAddress = " +remoteAddress);

            tcpBuf.clear();
            String msg = "";
            if (null != clientAddressMap.get(remoteAddress)) {
                msg = Protocol.ERROR_SYMBOL.getSymbol() +
                        "连接错误，检测到你正在游戏中，请退出重连！" +
                        Protocol.ERROR_SYMBOL.getSymbol();
            } else {
                msg = Protocol.CONNECT_SYMBOL.getSymbol() +
                        UUID.randomUUID().toString() + Protocol.CONNECT_SYMBOL.getSymbol();
            }
            tcpBuf.put(msg.getBytes());
            clientChannel.write(tcpBuf);
            System.out.println("已分配端口号给客户端");
        } catch (IOException e) {
            System.err.println("客户端接入服务器时发生错误" + e.getMessage());
            turnOff();
        }
    }

    /**
     *  从给出的网络地址中截取IP地址
     * @param address
     * @return
     */
    private String getIPFromSocketAddress(SocketAddress address) {
        String s = address.toString();
        return s.substring(1, s.indexOf(":"));
    }

    public static void main(String[] args) {
        ServerNet serverNet = new ServerNet();
        serverNet.turnOn();
        Thread tcpMonitorThread = new Thread(() -> {
            serverNet.tcpMonitor();
        }, "tcp_Monitor_Thread");
        Thread udpMonitorThread = new Thread(() -> {
            serverNet.udpMonitor();
        },"udp_Monitor_Thread");
       try {
           tcpMonitorThread.join();
           udpMonitorThread.join();
       } catch (InterruptedException e) {
           System.err.println("tcp_Monitor_Thread 或 udp_Monitor_Thread在运行中被异常打断！" + e.getMessage());
           serverNet.turnOff();
       }
       tcpMonitorThread.start();
       udpMonitorThread.start();
    }


}
